﻿using System;
using System.Collections.Generic;
using System.IO;
using ooWii.IO;

namespace ooWii.Collections
{
	public class DiscList : List<IDiscInfo>, IDisposable
	{
		#region DiscList

		List<Stream> streams = new List<Stream>();

		public DiscList( string location )
		{
			if( location.StartsWith( @"\\.\" ) )
			{
				loadGamesFromVolume( location );
				return;
			}

			string pattern = Path.GetFileName( location );
			string path = Path.GetDirectoryName( location );

			if( pattern.IndexOfAny( new char[] { '?', '*' } ) >= 0 )
			{
				if( path == string.Empty )
				{
					path = ".";
				}

				DirectoryInfo directoryInfo2 = new DirectoryInfo( path );
				if( directoryInfo2.Exists )
				{
					loadGames( directoryInfo2.GetFiles( pattern ) );
					loadGamesFromDirectories( directoryInfo2.GetDirectories( pattern ) );
					return;
				}
			}

			DirectoryInfo directoryInfo = new DirectoryInfo( location );
			if( directoryInfo.Exists )
			{
				loadGames( directoryInfo.GetFiles() );
				loadGamesFromDirectories( directoryInfo.GetDirectories() );
				return;
			}

			FileInfo fileInfo = new FileInfo( location );
			if( fileInfo.Exists )
			{
				loadGame( fileInfo );
				return;
			}
		}

		void loadGamesFromVolume( string volume )
		{
			Stream stream = new VolumeStream( volume );

			try
			{
				byte[] buffer = new byte[ 6 ];
				if( stream.Read( buffer, 0, buffer.Length ) != 6 )
				{
					stream.Close();
					throw new Exception( "too short" );
				}

				if( WiiBuffer.GetString( buffer, 0, 4 ) == "WBFS" )
				{
					loadGamesFromWbfs( volume, stream );
				}
				else
				{
					this.Add( new VolumeDiscInfo( volume, stream ) );
				}

				streams.Add( stream );
			}
			catch
			{
				stream.Close();
				throw;
			}
		}

		void loadGamesFromDirectories( DirectoryInfo[] directoryInfos )
		{
			for( int i = 0; i < directoryInfos.Length; i++ )
			{
				loadGames( directoryInfos[ i ].GetFiles( "*.wbfs" ) );
				loadGames( directoryInfos[ i ].GetFiles( "*.ciso" ) );
				loadGames( directoryInfos[ i ].GetFiles( "*.iso" ) );
			}
		}

		void loadGames( FileInfo[] fileInfos )
		{
			if( fileInfos.Length > 0 )
			{
				for( int i = 0; i < fileInfos.Length; i++ )
				{
					loadGame( fileInfos[ i ] );
				}
			}
		}

		void loadGame( FileInfo fileInfo )
		{
			string extension = Path.GetExtension( fileInfo.Name );

			if( extension == ".iso" )
			{
				this.Add( new IsoDiscInfo( fileInfo ) );
			}
			else if( extension == ".ciso" || extension == ".wbi" )
			{
				this.Add( new CisoDiscInfo( fileInfo ) );
			}
			else if( extension == ".wbfs" )
			{
				Stream stream = new SplitFileStream( fileInfo.FullName );

				try
				{
					loadGamesFromWbfs( fileInfo.FullName, stream );
					streams.Add( stream );
				}
				catch
				{
					stream.Close();
					throw;
				}
			}
		}

		void loadGamesFromWbfs( string location, Stream wbfs )
		{
			WbfsDiscList wbfsPartition = new WbfsDiscList( wbfs );

			for( int i = 0; i < wbfsPartition.Count; i++ )
			{
				this.Add( new WbfsDiscInfoWrapper( string.Format( "{0}/{1}", location, i ), wbfsPartition[ i ] ) );
			}
		}

		public void Dispose()
		{
			for( int i = 0; i < streams.Count; i++ )
			{
				streams[ i ].Close();
			}
			streams.Clear();
		}

		#endregion

		class VolumeDiscInfo : IDiscInfo
		{
			#region VolumeDiscInfo

			string id;
			string name;
			string location;
			Stream stream;

			public VolumeDiscInfo( string location, Stream stream )
			{
				WiiDiscHeader wiiDiscHeader = new WiiDiscHeader( stream );
				this.id = string.Format( "{0}{1}{2}{3}", wiiDiscHeader.DiscId, wiiDiscHeader.GameCode, wiiDiscHeader.RegionCode, wiiDiscHeader.MakerCode );
				this.name = wiiDiscHeader.GameTitle;
				this.location = location;
				this.stream = stream;
			}

			#endregion

			#region IDiscInfo

			public string Id
			{
				get
				{
					return id;
				}
			}

			public string Name
			{
				get
				{
					return name;
				}
			}

			public string Location
			{
				get
				{
					return location;
				}
			}

			public Stream OpenRead()
			{
				return new ReopenStream( stream );
			}

			#endregion
		}

		abstract class FileDiscInfo : IDiscInfo
		{
			#region FileDiscInfo

			string id;
			string name;
			protected FileInfo fileInfo;

			public FileDiscInfo( FileInfo fileInfo )
			{
				this.fileInfo = fileInfo;

				string name = fileInfo.Name;
				int length = name.Length;

				if( length >= 9 && name[ length - 9 ] == ' ' && name[ length - 8 ] == '[' && name[ length - 1 ] == ']' )
				{
					this.id = name.Substring( length - 7, 6 );
					this.name = name.Substring( 0, length - 9 );
				}
				else if( length > 6 && name[ 6 ] == '_' )
				{
					this.id = name.Substring( 0, 6 );
					this.name = name.Substring( 7 );
				}
				else if( length == 6 )
				{
					this.id = name;
					this.name = string.Empty;
				}
				else
				{
					this.id = string.Empty;
					this.name = name;
				}
			}

			#endregion

			#region IDiscInfo

			public string Id
			{
				get
				{
					return id;
				}
			}

			public string Name
			{
				get
				{
					return name;
				}
			}

			public string Location
			{
				get
				{
					return fileInfo.FullName;
				}
			}

			public abstract Stream OpenRead();

			#endregion
		}

		class IsoDiscInfo : FileDiscInfo
		{
			#region IDiscInfoIso

			public IsoDiscInfo( FileInfo fileInfo )
				: base( fileInfo )
			{
			}

			#endregion

			#region IDiscInfo

			public override Stream OpenRead()
			{
				return fileInfo.OpenRead();
			}

			#endregion
		}

		class CisoDiscInfo : FileDiscInfo
		{
			#region CisoDiscInfo

			public CisoDiscInfo( FileInfo fileInfo )
				: base( fileInfo )
			{
			}

			#endregion

			#region IDiscInfo

			public override Stream OpenRead()
			{
				return new CisoStream( fileInfo.OpenRead() );
			}

			#endregion
		}

		class WbfsDiscInfoWrapper : IDiscInfo
		{
			#region WbfsDiscInfoWrapper

			string location;
			WbfsDiscInfo discInfo;

			public WbfsDiscInfoWrapper( string location, WbfsDiscInfo discInfo )
			{
				this.location = location;
				this.discInfo = discInfo;
			}

			#endregion

			#region IDiscInfo

			public string Id
			{
				get
				{
					return discInfo.Id;
				}
			}

			public string Name
			{
				get
				{
					return discInfo.Name;
				}
			}

			public string Location
			{
				get
				{
					return location;
				}
			}

			public Stream OpenRead()
			{
				return discInfo.OpenRead();
			}

			#endregion
		}
	}
}
